/* Software I2C module (only 'write' requests slave)   grytole@gmail.com   10-Nov-2013 */

#include <avr/io.h>
#include <avr/interrupt.h>
#include <inttypes.h>
#include "i2c.h"

#define I2C_DDR (DDRD)
#define I2C_PORT (PORTD)
#define I2C_PIN (PIND)
#define I2C_SCL (PD2)
#define I2C_SDA (PD3)

#define I2C_SCL_READ ((I2C_PIN & (1 << I2C_SCL)) >> I2C_SCL)
#define I2C_SDA_READ ((I2C_PIN & (1 << I2C_SDA)) >> I2C_SDA)
#define I2C_SCL_SETHIGH (I2C_DDR &= ~(1 << I2C_SCL))
#define I2C_SCL_SETLOW (I2C_DDR |= (1 << I2C_SCL))
#define I2C_SDA_SETHIGH (I2C_DDR &= ~(1 << I2C_SDA))
#define I2C_SDA_SETLOW (I2C_DDR |= (1 << I2C_SDA))

#define I2C_DATA_END_POS (7)

#define I2C_STATE_STOPPED (0)
#define I2C_STATE_STARTED (1)
#define I2C_STATE_GETADDR (2)
#define I2C_STATE_GETSLOT (3)
#define I2C_STATE_GETDATA (4)
#define I2C_STATE_WAITING (5)

typedef struct {
    uint8_t *ptr;
    uint8_t  len;
} i2cSlot;

static volatile uint8_t m_i2cAddress;
static volatile uint8_t m_i2cState;
static volatile uint8_t m_i2cInData;
static volatile uint8_t m_i2cCurBit;
static volatile uint8_t m_i2cCurDataByte;
static volatile uint8_t m_i2cSlotId;
static volatile i2cSlot m_i2cSlotMap[I2C_SLOT_NUM];

/*** SCL line change handler ***/
ISR(INT0_vect)
{
    if((I2C_STATE_WAITING != m_i2cState) && (I2C_STATE_STOPPED != m_i2cState)) /* if device should react */
    {
        if(I2C_SCL_READ) /* raising - data valid */
        {
            if(I2C_DATA_END_POS >= m_i2cCurBit) /* if receiving data bits */
            {
                m_i2cInData |= (I2C_SDA_READ << (I2C_DATA_END_POS - m_i2cCurBit)); /* read bit */
            }
        }
        else /* falling - change allowed */
        {
            I2C_SCL_SETLOW; /* lock line before operations */

            if(I2C_STATE_STARTED == m_i2cState) /* if communication started */
            {
                m_i2cState = I2C_STATE_GETADDR;
            }
            else /* if communication in progress */
            {
                if(I2C_DATA_END_POS > m_i2cCurBit) /* if byte transmitting in progress */
                {
                    m_i2cCurBit++; /* start next bit */
                }
                else if(I2C_DATA_END_POS == m_i2cCurBit)
                {
                    if(I2C_STATE_GETADDR == m_i2cState) /* if getting address */
                    {
                        if(m_i2cAddress == m_i2cInData) /* if address is correct */
                        {
                            I2C_SDA_SETLOW; /* set ACK on data line for confirm */

                            m_i2cState = I2C_STATE_GETSLOT;
                        }
                        else /* if address is wrong */
                        {
                            m_i2cState = I2C_STATE_WAITING; /* do not respond anymore */
                        }
                    }
                    else if(I2C_STATE_GETSLOT == m_i2cState) /* if getting slot id */
                    {
                        if(I2C_SLOT_NUM > m_i2cInData) /* if slot id is allowed */
                        {
                            I2C_SDA_SETLOW; /* set ACK on data line for confirm */

                            m_i2cSlotId = m_i2cInData; /* save slot id */

                            m_i2cState = I2C_STATE_GETDATA;
                        }
                        else /* if slot id is wrong */
                        {
                            m_i2cState = I2C_STATE_WAITING; /* do not respond anymore */
                        }
                    }
                    else /* if receiving data */
                    {
                        if(m_i2cSlotMap[m_i2cSlotId].len > m_i2cCurDataByte)
                        {
                            I2C_SDA_SETLOW; /* set ACK on data line for confirm */

                            m_i2cSlotMap[m_i2cSlotId].ptr[m_i2cCurDataByte] = m_i2cInData; /* save data byte */

                            m_i2cCurDataByte++; /* start next data byte */
                        }
                    }

                    m_i2cCurBit++; /* start next bit */
                }
                else /* if complete byte was received */
                {
                    m_i2cCurBit = 0; /* repeat loop for next byte */
                    m_i2cInData = 0; /* clean buffer before next byte */
                    I2C_SDA_SETHIGH; /* free data line after confirm */
                }
            }

            I2C_SCL_SETHIGH; /* unlock line after operations */
        }
    }
}

/*** SDA line change handler ***/
ISR(INT1_vect)
{
    if(I2C_SCL_READ) /* START or STOP condition */
    {
        if(I2C_SDA_READ) /* STOP condition */
        {
            m_i2cState = I2C_STATE_STOPPED;
        }
        else /* START condition */
        {
            m_i2cState = I2C_STATE_STARTED;
            m_i2cInData = 0;
            m_i2cCurBit = 0;
            m_i2cSlotId = 0;
            m_i2cCurDataByte = 0;
        }
    }
}

/*** Module initialization ***/
void i2cInit(uint8_t address)
{
    uint8_t slot;

    I2C_PORT &= ~((1 << I2C_SDA) | (1 << I2C_SCL)); /* enable pull ups on i2c pins */
    I2C_SCL_SETHIGH;
    I2C_SDA_SETHIGH;

    m_i2cAddress = (address << 1); /* set device address */
    m_i2cState = I2C_STATE_STOPPED;
    m_i2cInData = 0;
    m_i2cCurBit = 0;
    m_i2cSlotId = 0;
    m_i2cCurDataByte = 0;

    for(slot = 0; slot < I2C_SLOT_NUM; slot++)
    {
        m_i2cSlotMap[slot].len = 0; /* by default slot is unusable */
    }

    MCUCR |= ((1 << ISC00) | (1 << ISC10)); /* any logical change on INT0 or INT1 activates interrupts */
    GIMSK |= ((1 << INT0) | (1 << INT1)); /* activate external interrupts for I2C lines */
}

/*** Bind slot with data location ***/
void i2cBindSlot(uint8_t slot, uint8_t *dataPtr, uint8_t dataLength)
{
    if(I2C_SLOT_NUM > slot)
    {
        m_i2cSlotMap[slot].ptr = dataPtr;
        m_i2cSlotMap[slot].len = dataLength;
    }
}

/* eof */